import { BaseService } from '@/core/base.service';
import {
  BaseQueryDto,
  WorkOrderDto,
  WorkOrderProcessDto,
  WorkOrderQueryDto,
} from '@/dtos';
import { WorkOrderStatusEnum } from '@/enums';
import { getCode, halt } from '@/utils';
import { Injectable } from '@nestjs/common';
import { Prisma, WorkOrder } from '@prisma/client';
import dayjs from 'dayjs';

@Injectable()
export class WorkOrderService extends BaseService {
  async list(
    { params, sort }: BaseQueryDto<Prisma.WorkOrderWhereUniqueInput>,
    userId: number,
  ) {
    const { current = 1, pageSize = 20, keyword, ...where } = params;

    let sql = `
      SELECT #{fields} FROM WorkOrder AS t
      WHERE t.isDeleted = false
        ${!!where.title ? `AND t.title LIKE '%${where.title}%'` : ''}
        ${!!where.type ? `AND t.type = ${where.type}` : ''}
        ${!!where.status ? `AND t.status = ${where.status}` : ''}
        ${!!userId ? `AND t.userId = ${userId}` : ''}
    `;

    const [{ count }] = await this.prisma.$queryRawUnsafe<[{ count: number }]>(
      sql.replace('#{fields}', 'COUNT(t.id) AS count'),
    );

    const list = await this.prisma.$queryRawUnsafe<WorkOrder[]>(
      `${sql.replace(
        '#{fields}',
        `
          t.id, t.no, t.title, t.type, t.orderNo, t.status, t.reason, 
          t.userId, t.orderNo, t.processedAt, t.createdAt
        `,
      )}
      ORDER BY ${
        !!sort && Object.keys(sort).length > 0
          ? Object.entries(sort)
              .map(([k, v]) => `t.${k} ${v === 'descend' ? 'DESC' : 'ASC'}`)
              .join(',')
          : 't.createdAt DESC'
      }
      LIMIT ${pageSize} 
      OFFSET ${(current - 1) * pageSize}
      `,
    );

    const total = count ? Number(count) : 0;
    const totalPage = Math.ceil(total / pageSize);
    const hasMore = current < totalPage;

    return {
      total,
      totalPage,
      hasMore,
      current,
      pageSize,
      list,
    };
  }

  async query({ params, sort }: WorkOrderQueryDto) {
    const { current = 1, pageSize = 20, keyword, ...where } = params;

    let sql = `
      SELECT #{fields} FROM WorkOrder AS t
      LEFT JOIN User AS u ON t.userId = u.id
      WHERE t.isDeleted = false
        ${!!where.title ? `AND t.title LIKE '%${where.title}%'` : ''}
        ${!!where.no ? `AND t.no LIKE '%${where.no}%'` : ''}
        ${!!where.orderNo ? `AND t.orderNo LIKE '%${where.orderNo}%'` : ''}
        ${!!where.mobile ? `AND u.mobile LIKE '%${where.mobile}%'` : ''}
        ${!!where.realname ? `AND u.realname LIKE '%${where.realname}%'` : ''}
        ${!!where.type ? `AND t.type = ${where.type}` : ''}
        ${!!where.status ? `AND t.status = ${where.status}` : ''}
    `;

    const [{ count }] = await this.prisma.$queryRawUnsafe<[{ count: number }]>(
      sql.replace('#{fields}', 'COUNT(t.id) AS count'),
    );

    const list = await this.prisma.$queryRawUnsafe<WorkOrder[]>(
      `${sql.replace(
        '#{fields}',
        `
          t.id, t.no, t.title, t.type, t.info, t.images, t.status, t.createdAt,
          t.reason, t.userId, t.orderNo, t.processedAt, u.realname, u.avatar, u.mobile
        `,
      )}
      ORDER BY ${
        !!sort && Object.keys(sort).length > 0
          ? Object.entries(sort)
              .map(([k, v]) => `t.${k} ${v === 'descend' ? 'DESC' : 'ASC'}`)
              .join(',')
          : 't.createdAt DESC'
      }
      LIMIT ${pageSize} 
      OFFSET ${(current - 1) * pageSize}
      `,
    );

    const total = count ? Number(count) : 0;
    const totalPage = Math.ceil(total / pageSize);
    const hasMore = current < totalPage;

    return {
      total,
      totalPage,
      hasMore,
      current,
      pageSize,
      list,
    };
  }

  async findOne(
    where: Prisma.WorkOrderWhereUniqueInput,
    select?: Prisma.WorkOrderSelect,
  ): Promise<WorkOrder | null> {
    return this.prisma.workOrder.findUnique({
      where,
      select,
    });
  }

  async create(data: Prisma.WorkOrderCreateInput): Promise<WorkOrder> {
    return this.prisma.workOrder.create({
      data,
    });
  }

  async update(params: {
    where: Prisma.WorkOrderWhereUniqueInput;
    data: Prisma.WorkOrderUpdateInput;
  }): Promise<WorkOrder> {
    const { where, data } = params;
    return this.prisma.workOrder.update({
      where,
      data,
    });
  }

  async remove(where: Prisma.WorkOrderWhereUniqueInput): Promise<WorkOrder> {
    return this.prisma.workOrder.update({
      where,
      data: {
        isDeleted: true,
      },
    });
  }

  async order(dto: WorkOrderDto, userId: number) {
    const user = await this.prisma.user.findUnique({
      where: {
        id: userId,
      },
    });
    if (!user) {
      return halt('用户信息异常');
    }
    if (!user.isReal) {
      return halt('请先完成实名认证');
    }
    return await this.redlock.using([`work:${userId}`], 10000, {}, async () => {
      if (
        (await this.prisma.workOrder.count({
          where: {
            status: {
              in: [WorkOrderStatusEnum.PENDING, WorkOrderStatusEnum.PROCESSING],
            },
            userId,
          },
        })) >= 15
      ) {
        return halt('每人同时只能提交15个工单');
      }
      const no = await this.generateOrderNo();
      return await this.prisma.workOrder.create({
        data: {
          ...dto,
          no,
          userId,
        },
      });
    });
  }

  async process(dto: WorkOrderProcessDto) {
    return await this.prisma.workOrder.update({
      where: {
        id: dto.id,
      },
      data: {
        reason: dto.reason,
        processedAt: new Date(),
        status: WorkOrderStatusEnum.COMPLETED,
      },
    });
  }

  async getOrderByNo(no: string) {
    const order = await this.prisma.workOrder.findFirst({
      where: {
        no,
        isDeleted: false,
      },
      select: {
        id: true,
        no: true,
        type: true,
        title: true,
        info: true,
        images: true,
        reason: true,
        status: true,
        processedAt: true,
        createdAt: true,
      },
    });
    if (!order) {
      return halt('工单信息异常');
    }
    return {
      ...order,
    };
  }

  async generateOrderNo() {
    return `${dayjs().format('YYYYMMDDHHmmss')}${await getCode(11)}`;
  }
}
